/*** Eclipse Class Decompiler plugin, copyright (c) 2012 Chao Chen (cnfree2000@hotmail.com) ***/
package cn.com.zte.ngbc.base.expression.enhance;

import cn.com.zte.ngbc.base.logging.LogManager;
import cn.com.zte.ngbc.base.logging.SysLog;
import cn.com.zte.ngbc.base.util.ClassUtils;
import cn.com.zte.ngbc.frw.model.util.ProfileSwitcher;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewConstructor;
import javassist.LoaderClassPath;
import ognl.Node;
import ognl.OgnlContext;
import ognl.enhance.ContextClassLoader;
import ognl.enhance.EnhancedClassLoader;
import ognl.enhance.ExpressionAccessor;
import ognl.enhance.ExpressionCompiler;

public class OgnlCompiler extends ExpressionCompiler {
	private static AtomicInteger classCounter = new AtomicInteger();
	private static final SysLog log = LogManager.getSysLog(OgnlCompiler.class);

	public <T> T genOneMethodClass(Class<T> type, String name, String code, String methodName,
			Class<?>[] parameterTypes) throws Exception {
		name = name.replace(".", "_").replace("/", "_");
		String getBody = "{\r\n" + code + "}";

		OgnlContext context = new OgnlContext();

		EnhancedClassLoader loader = getClassLoader(context);
		ClassPool pool = getClassPool(context, loader);

		CtClass newClass = pool
				.makeClass(type.getSimpleName() + "_" + name + "_" + classCounter.getAndIncrement() + "_CodeGen");
		newClass.addInterface(getCtClass(type));

		CtClass[] objClsas = new CtClass[parameterTypes.length];
		for (int i = 0; i < objClsas.length; ++i)
			objClsas[i] = getCtClass(parameterTypes[i]);
		try {
			if (ProfileSwitcher.get().printGenCode) {
				log.debug("Class :" + newClass.getName() + ", Method: " + methodName);
				log.debug("Parameters :");
				for (int i = 0; i < objClsas.length; ++i) {
					log.debug("p[" + i + "] : " + objClsas[i].getName());
				}
				log.debug("Generate Code:");
				log.debug(code);
			}

			CtMethod valueGetter = new CtMethod(CtClass.voidType, methodName, objClsas, newClass);
			valueGetter.setBody(getBody);
			newClass.addMethod(valueGetter);

			newClass.addConstructor(CtNewConstructor.defaultConstructor(newClass));

			Class clazz = pool.toClass(newClass);
			newClass.detach();

			return (T) clazz.newInstance();
		} catch (Throwable t) {
			throw new RuntimeException("Error compiling mapping code '" + name + "' " + methodName + ": " + getBody, t);
		}
	}

	public ExpressionAccessor genClass(OgnlType root, Node expression, String getter, String setter) throws Exception {
		String getBody = "{\r\nognl.OgnlContext context = $1;\r\n" + ClassUtils.getClassName(root.getType())
				+ " _this = (" + ClassUtils.getClassName(root.getType()) + ")$2;\r\n" + getter + "}";

		String setBody = "{throw new UnsupportedOperationException();}";

		OgnlContext context = new OgnlContext();

		EnhancedClassLoader loader = getClassLoader(context);
		ClassPool pool = getClassPool(context, loader);

		CtClass newClass = pool
				.makeClass("OgnlExpr_" + expression.hashCode() + "_" + classCounter.getAndIncrement() + "_Accessor");
		newClass.addInterface(getCtClass(ExpressionAccessor.class));

		CtClass ognlClass = getCtClass(OgnlContext.class);
		CtClass objClass = getCtClass(Object.class);
		try {
			if (ProfileSwitcher.get().printGenCode) {
				log.debug("Class :" + newClass.getName());
				log.debug("Generate getBody Code: " + getBody);
				log.debug("Generate setBody Code: " + setBody);
			}

			CtMethod valueGetter = new CtMethod(objClass, "get", new CtClass[] { ognlClass, objClass }, newClass);
			valueGetter.setBody(getBody);
			newClass.addMethod(valueGetter);

			CtMethod valueSetter = new CtMethod(CtClass.voidType, "set",
					new CtClass[] { ognlClass, objClass, objClass }, newClass);
			valueSetter.setBody(setBody);
			newClass.addMethod(valueSetter);

			newClass.addConstructor(CtNewConstructor.defaultConstructor(newClass));

			Class clazz = pool.toClass(newClass);
			newClass.detach();

			return ((ExpressionAccessor) clazz.newInstance());
		} catch (Throwable t) {
			throw new RuntimeException("Error compiling expression '" + expression + "' getter body: " + getBody
					+ " setter body: " + setBody, t);
		}
	}

	protected EnhancedClassLoader getClassLoader(OgnlContext context) {
		EnhancedClassLoader ret = (EnhancedClassLoader) this._loaders.get(context.getClassResolver());

		if (ret != null) {
			return ret;
		}
		ClassLoader classLoader = new ContextClassLoader(Thread.currentThread().getContextClassLoader(), context);

		ret = new EnhancedClassLoader(classLoader);
		this._loaders.put(context.getClassResolver(), ret);

		return ret;
	}

	protected ClassPool getClassPool(OgnlContext context, EnhancedClassLoader loader) {
		if (this._pool != null) {
			return this._pool;
		}
		this._pool = new ClassPool(null);
		this._pool.appendSystemPath();
		this._pool.insertClassPath(new LoaderClassPath(loader.getParent()));

		return this._pool;
	}
}